package com.googlecode.dgwt.client.dojo.impl;

import java.util.AbstractList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import com.googlecode.dgwt.client.dojo.Dojo;
import com.googlecode.dgwt.client.dojo.core.JsFunction;
import com.googlecode.dgwt.client.dojo.core.JsMap;
import com.googlecode.dgwt.client.dojo.core.JsObject;
import com.googlecode.dgwt.client.dojo.core.NativeWrapper;
import com.googlecode.dgwt.client.dojo.reflect.BeanField;
import com.googlecode.dgwt.client.dojo.reflect.BeanInfo;
import com.googlecode.dgwt.client.dojo.reflect.BeanMethod;
import com.googlecode.dgwt.client.dojo.reflect.BeanParam;
import com.googlecode.dgwt.client.dojo.reflect.DojoBean;
import com.googlecode.dgwt.client.dojo.string.RegExp;
import com.googlecode.dgwt.client.dojo.string.RegExpResult;
import com.googlecode.dgwt.client.dojo.util.StringMap;

/**
 * @author wellsantos@wellsantos.com
 * @since 0.01
 */
public abstract class ReflectionImpl implements NativeWrapper {


    protected final RegExp simpleNameRegExp = RegExp.create("\\.?(\\w+)$");    
    protected final Map<String, BeanInfo<?>> infoMap = new StringMap<BeanInfo<?>>();
    
    private final BeanMethod<Object> emptyGetter;
    private final BeanMethod<Object> emptySetter;
    private final BeanField<Object> emptyMethodInfo;
    
    
    protected ReflectionImpl() {
        emptyGetter = new BeanMethodImpl("get", Object.class, new BeanParam[0]) {
            
            @Override
            public native JsFunction getFunction() /*-{
                return function(){ return null; };
            }-*/;
            
        };
        emptySetter = new BeanMethodImpl("set", null, new BeanParam[] {
                new BeanParam("value", Object.class, "Ljava/lang/Object;")}) {
            
            @Override
            public native JsFunction getFunction() /*-{
                return function(value){ };
            }-*/;
            
        };
        emptyMethodInfo = new BeanField<Object>("", emptyGetter, emptySetter);
    }
    
    
    public String getSimpleName(Class<?> classType) {
        RegExpResult result = null;
        if (classType != null) {
            result = simpleNameRegExp.exec(classType.getName());
        }
        return result != null ? result.get(1).replace('$', '.') : "";
    }
    
    
    public boolean isSuperClass(Class<?> parent, Class<?> child) {
        do {
            if (child == parent) {
                return true;
            }
            child = child.getSuperclass();
        } while (child != Object.class);
        return child == parent;
    }

    
    public boolean isAssignableFrom(Class<?> parent, Class<?> type) {
        return isSuperClass(parent, type);
    }

    
    @SuppressWarnings("unchecked")
    public <T> BeanField<T> getEmptyMethodInfo() {
        return (BeanField<T>) emptyMethodInfo;
    }
    

    @SuppressWarnings("unchecked")
    public <T> BeanMethod<T> getEmptyMethodGetter() {
        return (BeanMethod<T>) emptyGetter;
    }
    

    @SuppressWarnings("unchecked")
    public <T extends DojoBean> BeanMethod<T> getEmptyMethodSetter() {
        return (BeanMethod<T>) emptySetter;
    }
    
    
    protected abstract <T extends Cloneable> T clone(T obj);
    

    @SuppressWarnings("unchecked")
    public <T extends DojoBean> BeanInfo<T> getBeanInfo(Class<T> beanClass) {
        
        if (beanClass == null) {
            return null;
        }
        
        do {
            BeanInfo<?> info = infoMap.get(beanClass.getName());
            
            if (info != null) {
                return (BeanInfo) info;
            }
            
            beanClass = (Class) beanClass.getSuperclass();
            
        } while (beanClass != Object.class);
        
        return (BeanInfo) infoMap.get(Object.class.getName());
    }
    
    
    static abstract class BeanMethodImpl extends BeanMethod<Object>{
        
        protected BeanMethodImpl(){
        }
        
        public BeanMethodImpl(String name, Class<?> returnType, BeanParam[] params) {
            super();
            init(name, returnType, params, getFunction());
        }

        public abstract JsFunction getFunction();
        
    }
    
    
    static class BeanInfoImpl extends BeanInfo<DojoBean> implements DojoBean {

        protected Class<DojoBean> classType;
        protected List<BeanMethod<DojoBean>> methods;
        protected Map<String,BeanField<DojoBean>> fields;
        
        private BeanInfoImpl() {
        }
        
        @SuppressWarnings("unchecked")
        protected static BeanInfoImpl create(final Class classType, 
                                          final BeanMethod[] methods,
                                          final BeanField[] fieldMethods){
            
            BeanInfoImpl impl = new BeanInfoImpl();
            impl.classType = classType;
            
            impl.methods = new AbstractList<BeanMethod<DojoBean>>() {

                @Override
                public BeanMethod<DojoBean> get(int index) {
                    return methods[index];
                }

                @Override
                public int size() {
                    return methods.length;
                }
                
            };
            
            impl.fields = new StringMap<BeanField<DojoBean>>() {
                
                private static final long serialVersionUID = 1L;
                
                public StringMap<BeanField<DojoBean>> init(){
                    for (BeanField<?> info : fieldMethods) {
                        super.put(info.getFieldName(), (BeanField<DojoBean>) info);
                    }
                    return this;
                }
                
                @Override
                public void clear() {
                    throw new UnsupportedOperationException();
                }

                @Override
                public BeanField<DojoBean> put(String key, BeanField<DojoBean> value) {
                    throw new UnsupportedOperationException();
                }

                @Override
                public BeanField<DojoBean> remove(Object key) {
                    throw new UnsupportedOperationException();
                }                
                
            }.init();
            
            return impl;
        }
        
        @Override
        public String getName() {
            return Dojo.reflect.getSimpleName(classType);
        }

        @Override
        public Class<DojoBean> getType() {
            return classType;
        }
        
        @Override
        public List<BeanMethod<DojoBean>> getMethods() {
            return methods;
        }

        @Override
        public Map<String,BeanField<DojoBean>> getFields() {
            return fields;
        }

        @Override
        public List<BeanMethod<DojoBean>> getConstructors() {
            // TODO: ReplectionImpl.getConstructors()
            return Collections.emptyList();
        }

    }


    @Override
    public JsObject toJavaScript() {
        JsMap<JsObject> map = JsMap.create();
        
        for (String key : infoMap.keySet()) {
            map.set(key, infoMap.get(key).toJavaScript());
        }
        
        return map;
    }

}